#   Copyright (c) 2008 ToI-Planning, All rights reserved.
# 
#   Redistribution and use in source and binary forms, with or without
#   modification, are permitted provided that the following conditions
#   are met:
# 
#   1. Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#
#   2. Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
#   3. Neither the name of the authors nor the names of its contributors
#      may be used to endorse or promote products derived from this
#      software without specific prior written permission.
#
#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
#   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#  $Id: App.pm 7 2008-09-28 16:26:09Z usualoma $

package CategorySearch::App;

use strict;

sub init_request {
	my ($plugin, $app) = @_;
	my $enable = $app->can('param') && $app->param('CategorySearch');

	if ($app->isa('MT::App::Search') && $enable) {
		local $SIG{__WARN__} = sub {  }; 
		if ($MT::VERSION < 4.2) {
			my $hit_method = \&MT::App::Search::_search_hit;
			*MT::App::Search::_search_hit = sub {
				return &_search_hit($hit_method, @_);
			};
		}
		else {
			if ($app->param('CategorySearchIgnoreString')) {
				$app->param('search', 'abc');
			}

			my $search_terms = \&MT::App::Search::search_terms;
			*MT::App::Search::search_terms = sub {
				return &search_terms($search_terms, @_);
			};

			require MT::Template::Context::Search;
			my $context_script = \&MT::Template::Context::Search::context_script;
			*MT::Template::Context::Search::context_script = \&context_script;
		}
	}
}

sub context_script {
	my ( $ctx, $args, $cond ) = @_;

	require MT;
	my $app = MT->instance;

    my $cgipath = $ctx->_hdlr_cgi_path($args);
    my $script = $ctx->{config}->SearchScript;

	my @ignores = ('startIndex', 'limit', 'offset', 'format');
    my $q = new CGI('');
	if ($app->isa('MT::App::Search')) {
	    foreach my $p ($app->param) {
			if (! grep({ $_ eq $p } @ignores)) {
				$q->param($p, $app->param($p));
			}
		}
	}

	local $CGI::USE_PARAM_SEMICOLONS;
	$CGI::USE_PARAM_SEMICOLONS = 0;
	$cgipath . $script . '?' . $q->query_string;
}

sub search_terms {
	my $search_terms = shift;
    my ($app) = @_;
	my ($terms, $args) = $search_terms->(@_);

	my @csets = $app->param('CategorySearchSets');
	my @cats0 = ();
	foreach my $cs (@csets) {
		my @queries = grep({
            my $str = $_;
            $str = s/^\s*(.*?)\s*$/$1/;
            $str;
        } $app->param($cs));
		if (! @queries) {
			next;
		}

		my $type = lc($app->param($cs . '_type') || 'or');
		my @cats1 = ();
		foreach my $query (@queries) {
			push(@cats1,
                (scalar(@cats1) ? ('-' . $type) : ()),
                { 'label' => $query }
            );
		}

        push(@cats0,
            (scalar(@cats0) ? '-or' : ()),
            \@cats1
        );
	}


    require MT::Entry;

    my $tmp_terms = @cats0 ?  [\@cats0] : [];

    push(@$tmp_terms, '-and', {
        id      => \'= placement_category_id',
        blog_id => \'= entry_blog_id',
    });

    require MT::Placement;
    require MT::Category;
    my $join_on = MT::Placement->join_on(
        undef,
        { entry_id => \'= entry_id', blog_id => \'= entry_blog_id' },
        {
            join   => MT::Category->join_on( undef, $tmp_terms, {} ),
            unique => 1
        }
    );

	my $count_ge = int((scalar(@cats0) + 1) / 2);
    my $counter = MT::Entry->driver->_do_group_by(
		" entry_id ",
		'MT::Entry',
        undef,
        {
            'comment' => 'searchtest',
            'join' => $join_on,
			'group' => [ 'id' ],
			'having' => {
				'COUNT(*)' => \('>= ' . $count_ge),
			},
        }
    );

	my @ids = ();
	while (my @row = $counter->()) {
		push(@ids, $row[0]);
	}

	my $where = undef;
	for (my $i = scalar(@$terms); $i >= 0; $i--) {
		if ((ref $terms->[$i]) eq 'ARRAY') {
			$where = $terms->[$i];
		}
	}

	if ($app->param('CategorySearchIgnoreString')) {
		while (@$where) {
			shift(@$where);
		}
    }

	push(@$where, (scalar(@$where) ? '-and' : ()), {
		'id' => (@ids ? \@ids : \' IS NULL'),
	});

	($terms, $args);
}

sub _search_hit {
    my ($hit_method, $app, $entry) = @_;

	if (! $app->param('CategorySearchIgnoreString')) {
        return 1 if &{$hit_method}($app, $entry);
    }
    return 0 if $app->{searchparam}{SearchElement} ne 'entries';

	my @status0 = 0;
    my $cats = $entry->categories;
	my @csets = $app->param('CategorySearchSets');
	foreach my $cs (@csets) {
		my @status1 = ();
		my @queries = grep({ $_ = s/^\s*(.*?)\s*$/$1/; $_ } $app->param($cs));
		if (! @queries) {
			push(@status0, 1);
		}
		else {
			foreach my $query (@queries) {
				my $stat = 0;
				for my $c (@$cats) {
					if ($c->label eq $query) {
						$stat = 1;
						last;
					}
				}
				push(@status1, $stat);
			}
			my $type = lc($app->param($cs . '_type') || 'or');
			if ($type eq 'and') {
				push(@status0, ! scalar(grep({ ! $_ } @status1)));
			}
			else {
				push(@status0, scalar(grep({ $_ } @status1)));
			}
		}
	}

    return (! scalar(grep({ ! $_ } @status0)));
}

1;
